﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ThirdArm.Dialogs;
using System.IO;
using ThirdArm.Forms;

namespace SEPHistoryViewer
{
    public partial class Main : Form
    {
        enum HistoryType
        {
            Unknown,
            WindowsLiveToolbar,
            BingToolbar
        }

        struct HistoryFileInfo
        {
            public string Path;
            public int FileSize;
            public int Version;
            public int DataSize;
            public HistoryType Type;
            public int RecordCount;
            public int NextRecordId;
            public int ProcessedCount;
        }

        struct WindowsLiveToolbarRecord
        {
            public int Header;
            public int RecordID;            
            public DateTime LastSearch;
            public int SearchCount;
            public int TermLength;
            public string Term;
        }

        struct BingToolbarRecord
        {
            public int Header;
            public int RecordSize;
            public int Unknown1;
            public int RecordID;
            public int Unknown2;
            public int SearchCount;
            //int LSYear;
            //int LSMonth;
            public int Unknown6;
            //int LSDate;
            //int LSHour;
            //int LSMinute;
            //int LSSecond;
            public int Unknown11;
            public DateTime LastSearch;
            public int TermLength;
            public string Term;
        }

        LogViewer _logViewer = new LogViewer();
        HistoryType _historyType = HistoryType.Unknown;

        public Main()
        {
            InitializeComponent();
            tslblInfo.Text = string.Empty;
            _logViewer.UseTimestamp = true;
            _logViewer.AddText("SEPHistoryViewer started.");

            olvRecords.BeginUpdate();
            foreach (ColumnHeader ch in olvRecords.Columns)
            {
                ch.AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
            }
            olvRecords.EndUpdate();
        }

        private void enableGUI(bool Value)
        {
            toolstrip.Enabled = Value;
        }

        private void setOlvObjects(System.Collections.IEnumerable collection)
        {
            olvRecords.BeginUpdate();
            olvRecords.SetObjects(collection);
            foreach (ColumnHeader ch in olvRecords.Columns)
            {
                ch.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
                int contentWidth = ch.Width;
                ch.AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
                if (ch.Width < contentWidth)
                {
                    ch.Width = contentWidth;
                }
            }
            olvRecords.EndUpdate();
        }

        private void tsbtnOpen_Click(object sender, EventArgs e)
        {
            FileSelectDialog fsd = new FileSelectDialog();
            fsd.Filter = "DAT Files|*.dat|All Files|*";
            fsd.Description = "Select file to process...";            
            if (fsd.ShowDialog() == DialogResult.OK)
            {
                _logViewer.AddText(string.Format("Input file is '{0}'.", fsd.FilePath));
                Text = string.Format("SEPHistoryViewer [{0}]", fsd.FilePath);
                tslblInfo.Text = "Processing...";
                enableGUI(false);
                bgwProcessor.RunWorkerAsync(fsd.FilePath);
            }
        }

        private void bgwProcessor_DoWork(object sender, DoWorkEventArgs e)
        {
            // Identify the file
            HistoryFileInfo hfInfo = new HistoryFileInfo();
            hfInfo.Path = (string)e.Argument;
            object[] r = new object[2];
            try
            {
                byte[] allFile = File.ReadAllBytes(hfInfo.Path);
                hfInfo.FileSize = allFile.Length;
                hfInfo.RecordCount = -1;
                
                switch (BitConverter.ToInt32(allFile, 0))
                {
                    case 0x27101962:
                        r[1] = processWindowsLiveToolbar(ref hfInfo, ref allFile);
                        break;
                    case 0xFACADE0:
                        r[1] = processBingToolbar(ref hfInfo, ref allFile);
                        break;
                    default:
                        r[1] = null;
                        hfInfo.Type = HistoryType.Unknown;
                        break;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                r[1] = null;
            }

            r[0] = hfInfo;
            e.Result = r;            
        }

        private List<WindowsLiveToolbarRecord> processWindowsLiveToolbar(ref HistoryFileInfo hfInfo, ref byte[] allFile)
        {
            hfInfo.Type = HistoryType.WindowsLiveToolbar;            

            hfInfo.RecordCount = BitConverter.ToInt32(allFile, 8);

            // Attempt to read all the records
            DateTime epoch = new DateTime(1601, 1, 1, 0, 0, 0);
            List<WindowsLiveToolbarRecord> records = new List<WindowsLiveToolbarRecord>();
            int pointer = 16;
            for (int i = 0; i < hfInfo.RecordCount; i++)
            {
                WindowsLiveToolbarRecord r = new WindowsLiveToolbarRecord();

                r.Header = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                r.RecordID = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                r.LastSearch = epoch.AddTicks(BitConverter.ToInt64(allFile, pointer));
                pointer += 8;

                r.SearchCount = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                r.TermLength = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;                

                r.Term = UnicodeEncoding.Unicode.GetString(allFile, pointer, r.TermLength * 2);
                pointer += r.TermLength * 2;

                if (r.RecordID != hfInfo.RecordCount) // Don't bother looking if it's the last record
                {
                    while (allFile[pointer] != 0x91 && pointer < hfInfo.FileSize)
                    {
                        pointer++;
                    }
                }

                records.Add(r);
            }

            hfInfo.ProcessedCount = records.Count;
            return records;
        }

        private List<BingToolbarRecord> processBingToolbar(ref HistoryFileInfo hfInfo, ref byte[] allFile)
        {
            hfInfo.Type = HistoryType.BingToolbar;

            hfInfo.Version = BitConverter.ToInt32(allFile, 4);
            hfInfo.NextRecordId = BitConverter.ToInt32(allFile, 8);
            //int CAFEBABE = BitConverter.ToInt32(allFile, 12);
            //int HdrUnknown1 = BitConverter.ToInt32(allFile, 16);
            hfInfo.RecordCount = BitConverter.ToInt32(allFile, 20);
            hfInfo.DataSize = BitConverter.ToInt32(allFile, 24);

            // Attempt to read all the records
            List<BingToolbarRecord> records = new List<BingToolbarRecord>();
            int pointer = 28;
            for (int i = 0; i < hfInfo.RecordCount; i++)
            {
                BingToolbarRecord r = new BingToolbarRecord();
                
                r.Header = BitConverter.ToInt32(allFile, pointer); // 0xFACEFEED
                pointer += 4;

                r.RecordSize = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                int nextRecordOffset = pointer + r.RecordSize;

                r.Unknown1 = BitConverter.ToInt32(allFile, pointer); // 0x19031991
                pointer += 4;

                r.RecordID = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                r.Unknown2 = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                r.SearchCount = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                int lsYear = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                int lsMonth = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                r.Unknown6 = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                int lsDate = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                int lsHour = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                int lsMinute = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                int lsSecond = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                r.LastSearch = new DateTime(lsYear, lsMonth, lsDate, lsHour, lsMinute, lsSecond);

                r.Unknown11 = BitConverter.ToInt16(allFile, pointer);
                pointer += 2;

                r.TermLength = BitConverter.ToInt32(allFile, pointer);
                pointer += 4;

                r.Term = UnicodeEncoding.Unicode.GetString(allFile, pointer, r.TermLength * 2);
                pointer = nextRecordOffset;

                records.Add(r);
            }

            hfInfo.ProcessedCount = records.Count;
            return records;
        }

        private void bgwProcessor_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                _logViewer.AddText("Process cancelled by the user.");
                tslblInfo.Text = "Cancelled.";
            }
            else
            {
                object[] r = (object[])e.Result;
                HistoryFileInfo hfInfo = (HistoryFileInfo)r[0];
                if (hfInfo.RecordCount == -1)
                {
                    tslblInfo.Text = string.Empty;
                    if (hfInfo.Type == HistoryType.Unknown)
                    {
                        _logViewer.AddText("The file you selected is not supported.");
                        MessageBox.Show("The file you selected is not supported.", "Not Supported", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    }
                    else
                    {
                        _logViewer.AddText("There was an error during processing.");
                        MessageBox.Show("There was an error during processing. Please check the log.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }                    
                }
                else
                {
                    _historyType = hfInfo.Type;
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("File Header Data:");
                    switch (hfInfo.Type)
                    {
                        case HistoryType.BingToolbar:                            
                            setOlvObjects((List<BingToolbarRecord>)r[1]);
                            sb.AppendLine("Type: Bing Toolbar");
                            break;
                        case HistoryType.WindowsLiveToolbar:                            
                            setOlvObjects((List<WindowsLiveToolbarRecord>)r[1]);
                            sb.AppendLine("Type: Windows Live Toolbar");
                            break;
                    }
                    
                    //sb.AppendFormat("Version: ", hfInfo.Version); // Not sure this is a file version
                    sb.AppendFormat("Path: {0}{1}", hfInfo.Path, Environment.NewLine);
                    sb.AppendFormat("File Size: {0:n0}{1}", hfInfo.FileSize, Environment.NewLine);

                    switch (hfInfo.Type)
                    {
                        case HistoryType.BingToolbar: // Data Size and Next Record ID only used by BingToolbar
                            sb.AppendFormat("Data Size: {0:n0}{1}", hfInfo.DataSize, Environment.NewLine);
                            sb.AppendFormat("Next Record ID: {0}{1}", hfInfo.NextRecordId, Environment.NewLine);
                            break;
                    }

                    sb.AppendFormat("Records Expected: {0:n0}{1}", hfInfo.RecordCount, Environment.NewLine);
                    sb.AppendFormat("Records Processed: {0:n0}", hfInfo.ProcessedCount);
                    _logViewer.AddText(sb.ToString());

                    _logViewer.AddText(string.Format("Completed, {0:n0} of {1:n0} records processed.{2}{2}", hfInfo.ProcessedCount, hfInfo.RecordCount, Environment.NewLine));                                        
                    tslblInfo.Text = string.Format("Completed, {0:n0} of {1:n0} records processed.", hfInfo.ProcessedCount, hfInfo.RecordCount);
                }
            }

            enableGUI(true);
        }

        private void tsbtnLog_Click(object sender, EventArgs e)
        {
            _logViewer.Show();
        }

        private void tsbtnExport_Click(object sender, EventArgs e)
        {
            if (_historyType == HistoryType.Unknown)
            {
                MessageBox.Show("Nothing to export!", "Export", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "CSV File|*.csv|All Files|*";
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                StringBuilder sbSave = new StringBuilder();
                switch (_historyType)
                {
                    case HistoryType.BingToolbar:
                        List<BingToolbarRecord> btRecs = (List<BingToolbarRecord>)olvRecords.Objects;
                        sbSave.AppendLine("RecordID,Term Length,Term,Search Count,Last Search");
                        foreach (BingToolbarRecord btr in btRecs)
                        {
                            sbSave.AppendFormat("{0},", Convert.ToString(btr.RecordID));
                            sbSave.AppendFormat("{0},", Convert.ToString(btr.TermLength));
                            sbSave.AppendFormat("\"{0}\",", btr.Term);
                            sbSave.AppendFormat("{0},", Convert.ToString(btr.SearchCount));
                            sbSave.AppendFormat("{0}", Convert.ToString(btr.LastSearch));
                            sbSave.AppendLine();
                        }
                        break;
                    case HistoryType.WindowsLiveToolbar:
                        List<WindowsLiveToolbarRecord> wltRecs = (List<WindowsLiveToolbarRecord>)olvRecords.Objects;
                        sbSave.AppendLine("RecordID,Term Length,Term,Search Count,Last Search");
                        foreach (WindowsLiveToolbarRecord wltr in wltRecs)
                        {
                            sbSave.AppendFormat("{0},", Convert.ToString(wltr.RecordID));
                            sbSave.AppendFormat("{0},", Convert.ToString(wltr.TermLength));
                            sbSave.AppendFormat("\"{0}\",", wltr.Term);
                            sbSave.AppendFormat("{0},", Convert.ToString(wltr.SearchCount));
                            sbSave.AppendFormat("{0}", Convert.ToString(wltr.LastSearch));
                            sbSave.AppendLine();
                        }
                        break;
                }

                try
                {
                    File.WriteAllText(sfd.FileName, sbSave.ToString());
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void tsbtnAbout_Click(object sender, EventArgs e)
        {
            new About().ShowDialog();
        }
    }
}
